package model

import (
	"bufio"
	"fmt"
	"github.com/fatih/color"
	"io"
	"net/http"
	"os"
	"sync"
)

// 全局HTTP客户端
var httpClient = http.Client{
	Transport: &http.Transport{
		// 关闭keep-alive确保一个线程就使用一个TCP连接
		DisableKeepAlives: true,
	},
}

// ShardTask 单个分片下载任务的任务参数和状态量
type ShardTask struct {
	// 下载链接
	Url string
	// 分片序号，从1开始
	Order int
	// 这个分片文件的路径
	ShardFilePath string
	// 分片的起始范围（字节，包含）
	RangeStart int64
	// 分片的结束范围（字节，包含）
	RangeEnd int64
	// 已下载的部分（字节）
	DownloadSize int64
	// 该任务是否完成
	TaskDone bool
}

// NewShardTask 构造函数
func NewShardTask(url string, order int, shardFilePath string, rangeStart int64, rangeEnd int64) *ShardTask {
	return &ShardTask{
		// 设定任务参数
		Url:           url,
		Order:         order,
		ShardFilePath: shardFilePath,
		RangeStart:    rangeStart,
		RangeEnd:      rangeEnd,
		// 初始化状态量
		DownloadSize: 0,
		TaskDone:     false,
	}
}

// DoShardGet 开始下载这个分片（该方法在goroutine中执行）
func (task *ShardTask) DoShardGet(waitGroup *sync.WaitGroup) {
	// 创建文件
	file, e := os.OpenFile(task.ShardFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if e != nil {
		color.Red("任务%d创建文件失败！", task.Order)
		color.HiRed("%s", e)
		return
	}
	// 准备请求
	request, e := http.NewRequest("GET", task.Url, nil)
	if e != nil {
		color.Red("任务%d创建请求出错！", task.Order)
		color.HiRed("%s", e)
		return
	}
	// 设定请求头
	request.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", task.RangeStart, task.RangeEnd))
	// 发送请求
	response, e := httpClient.Do(request)
	if e != nil {
		color.Red("任务%d发送下载请求出错！", task.Order)
		color.HiRed("%s", e)
		return
	}
	// 读取请求体
	body := response.Body
	// 读取缓冲区
	buffer := make([]byte, 8092)
	// 准备写入文件
	writer := bufio.NewWriter(file)
	for {
		// 读取一次内容至缓冲区
		readSize, readError := body.Read(buffer)
		if readError != nil {
			// 如果读取完毕则退出循环
			if readError == io.EOF {
				break
			} else {
				color.Red("任务%d读取响应错误！", task.Order)
				color.HiRed("%s", readError)
				return
			}
		}
		// 把缓冲区内容追加至文件
		_, writeError := writer.Write(buffer[0:readSize])
		if writeError != nil {
			color.Red("任务%d写入文件时出现错误！", task.Order)
			color.HiRed("%s", writeError)
			return
		}
		_ = writer.Flush()
		// 记录下载进度
		task.DownloadSize += int64(readSize)
	}
	// 关闭全部资源
	_ = body.Close()
	_ = file.Close()
	// 标记任务完成
	task.TaskDone = true
	// 使线程组中计数器-1
	waitGroup.Done()
}